解释下变量提升
// 通过var声明的变量会被提升到代码头部，但是输出的是 undefined js代码逐行执行
一段JavaScript代码是如何执行的
// 主线程中任务顺序执行，当主线程中任务执行完毕后会读取异步队列回调函数，将其放入主线程中执行 这个过程叫 event loop
    // js会有一个进程不断检查主线程的执行栈是否为空，为空的话就会去异步队列读取函数
    // 当主线程宏任务执行完毕后会清空微任务队列函数 然后才会执行下一个宏任务
    // macro-task(宏任务)：包括整体代码script，setTimeout，setInterval
    // micro-task(微任务)：Promise，process.nextTick
理解闭包
// 闭包是函数和函数体内可访问变量的总和
// 闭包的本质其实就是隐藏一个变量，或者说间接访问一个变量
// 闭包并不会引起内存泄漏，只是由于IE9 之前的版本对JScript对象和COM对象使用不同的垃圾收集，从而导致内存无法进行回收。
// 如果闭包被全局变量引用了就不会被释放了， 如果闭包是执行了则在执行完后释放
var theThing = null;
var replaceThing = function () {
    var originalThing = theThing;
    var unused = function () {
      if (originalThing) {
          console.log('hi');
      }
    };
    theThing = {
      longStr: new Array(100000000).join('*'),
      someMethod: function () {
          console.log('some message');
      }
    };
    console.log(process.memoryUsage().heapUsed);
};
setInterval(replaceThing, 100);
一个闭包泄漏的经典分析
// 闭包大家都知道，重点从两个方面来理解：
// 函数嵌套函数，内层函数引用了外层函数作用域下的变量，并且内层函数被全局环境下的变量引用，就形成了闭包。
// 函数内部定义的所有函数共享同一个闭包对象。
// 所以，看到一个函数先确定其中的函数嵌套关系，再看是否内层函数引用了外层函数作用域下的变量，这道题目中，内层函数unused引用了外层函数replaceThing作用域中的变量originalThing,
// 这里就有了一个闭包对象，假如没有后面的theThing对象，由于函数unused并没有被全局变量引用，这个闭包对象在函数执行完后就销毁了。

// 但是我们看后面的代码，someMethod函数组成一个大对象被赋值给了全局变量theThing，这里就符合了定义“内层函数被全局环境下的变量引用”，
// 但是很多人有疑问我这个方法里没有引用外层函数的变量啊，这里就正好符合了第二条定义“函数内部定义的所有函数共享同一个闭包对象”，
// 因此someMethod函数跟unused共享了同一个闭包对象，隐式持有了变量originalThing的引用，形成了一个 theThing -> someMethod -> originalThing ->上一次theThing -> ... 的循环引用， 造成了内存泄漏。
JavaScript的作用域链理解
// JavaScript属于静态作用域，即声明的作用域是根据程序正文在编译时就确定的，有时也称为词法作用域。 
// 其本质是JavaScript在执行过程中会创造可执行上下文，可执行上下文中的词法环境中含有外部词法环境的引用，
// 我们 可以通过这个引用获取外部词法环境的变量、声明等，这些引用串联起来一直指向全局的词法环境，因此形成了作用域链。
ES6模块与CommonJS模块有什么区别
// CommonJS 模块输出的是一个值的拷贝，ES6 模块输出的是值的引用。
//CommonJS 基本数据类型会复制/复杂数据类型属于浅拷贝 当使用require命令加载某个模块时，就会运行整个模块的代码。
//es6模块 import命令时，就会生成一个只读引用 动态的 等到脚本真正执行时，再根据这个只读引用，到被加载的那个模块里面去取值
        // 原始值发生变化，import加载的值也会发生变化。不论是基本数据类型还是复杂数据类型。
// 2、所谓值的拷贝，原始类型的值被缓存，不随模块内部的改变而改变。
// 3、ES6 模块是动态引用，不缓存值，模块内外是绑定的，而且是只读引用，不能修改值。ES6 的 js 引擎对脚本静态分析的时候，遇到加载命令模块 import ，就会生成一个只读引用，当真正用到模块里边的值的时候，就会去模块内部去取。
// 4、CommonJS 模块是运行时加载，ES6 模块是编译时加载输出接口。
// 5、运行时加载：CommonJS 模块就是对象；是先加载整个模块，生成一个对象，然后再从这个对象上面读取方法，这种加载称为“运行时加载”。
// 6、编译时加载： ES6模块不是对象，而是通过 export 命令「显式指定输出的代码」。import 时采用静态命令的形式，即在import指定「加载某个输出值」，而「不是加载整个模块」，这种加载称为“编译时加载”。
// 7、import的接口是read-only（只读状态），不能修改其变量值。 即不能修改其变量的指针指向，但可以改变变量内部指针指向,可以对commonJS对重新赋值（改变指针指向），但是对ES6 Module赋值会编译报错。
// ES6 Module和CommonJS模块的共同点：
// CommonJS和ES6 Module都可以对引入的对象进行赋值，即对对象内部属性的值进行改变。
js有哪些类型
// 原始类型: boolean null undefined number string symbol Symbol本质上是一种唯一标识符，可用作对象的唯一属性名 即使是用同一个变量生成的值也不相等 Symbol('id');
// 复杂类型:Object
null与undefined的区别是什么
// null表示为空，代表此处不应该有值的存在，一个对象可以是null，代表是个空对象。 
// undefined表示『不存在』，它是变量的一种取值，表明变量没有初始化
// 可以认为undefined是系统级的，出乎意料的或类似错误的值的空缺，比如未初始化。null是程序级的意料之中的值的空缺，比如我对某个参数或者变量设置的null
0.1+0.2为什么不等于0.3
// 由于计算机底层存储都是基于二进制的，需要事先由十进制转换为二进制存储与运算
// 向0.1,0.2这样的数是无穷尽的，无法二进制精确表示，就会保存有效储存位数52次方
// 这样在计算中就可能会引起精度的丢失
// 一般解决方法是保留位数
类型转换
// 显式
    // 转换为数值类型：Number(mix)、parseInt(string,radix)、parseFloat(string)
    // 转换为字符串类型：toString(radix)、String(mix)
    // 转换为布尔类型：Boolean(mix)
// 隐式
    // 逻辑运算，比较运算，加法运算，isNaN()等
原型
// 在构造函数创建的时候，会有一个原型对象，本质上是一个对象，当实例找不到属性的时候就会通过原型链在原型对象继续查找，逐级查找
原型链
// 是每个对象都有 __proto__ 属性，此属性指向该对象的构造函数的原型。
如何判断是否是数组
// Array.isArray
// 或者兼容 利用  Object.prototype.toString方法判断
if(!Array.isArray){ 
    Array.isArray = function(arg){ 
        return Object.prototype.toString.call(arg)==='[object Array]' 
    } 
}
this指向
// this的指向是在执行时候确定的，默认情况下this是指向全局对象的，比如在浏览器就是指向window
// 其次，如果函数被调用的位置存在上下文对象时，那么函数是被隐式绑定的 即谁调用了他他指向谁
// call/apply/bind改变this指向传进去的是谁this指向谁
// new创建的新对象 新对象会自动绑定到构造函数的this上，this指向这个对象
// 匿名函数的执行环境具有全局性，this对象通常指向window对象
// var name = 'The Window';
// var obj = {
//     name: 'My obj',
//     getName: function() {
//         return function() {
//             console.log(this.name);
//         };
//     }
// };
// obj.getName()(); // 'The Window'
箭头函数
// 箭头函数本身没有this，他的this是根据上下文的this作为自己的this,箭头函数中this其实就是他父函数中的this
async/await是什么
// async 是一个修饰符，async 定义的函数会默认的返回一个Promise对象resolve的值，因此对async函数可以直接进行then操作,返回的值即为then方法的传入函数
// await 关键字 只能放在 async 函数内部， await关键字的作用 就是获取 Promise中返回的内容， 获取的是Promise函数中resolve或者reject的值
// 如果await 后面并不是一个Promise的返回值，则会按照同步程序返回值处理
async/await相对于Promise的优势
// 代码读起来更加同步，Promise虽然摆脱了回调地狱，但是then的链式调用也会带来额外的阅读负担
// Promise传递中间值非常麻烦，而async/await几乎是同步的写法，非常优雅
// 错误处理友好，async/await可以用成熟的try/catch，Promise的错误捕获非常冗余 catch((err) => {})
// 调试友好 async/await可以像同步代码一样调试，promise中的then回调分步调试不会进入到后续的then回调
JavaScript的参数是按照什么方式传递的
// 基本类型传参是值传递
// 复杂类型传参 不是引用传递应该说是共享传递 传递的参数会先进行拷贝一份,他们的指针都是指向一个地方，方法里面操作的是这个副本 
// 直接对参数赋值的话其实是修改的这个副本，不影响原本的值，修改深层的就会修改原本的值
// 其实就是---复杂类型内存地址的复制传递
// 浅拷贝和赋值还是有差别的
// 浅拷贝相当于是新建了一个对象，然后对原对象属性值进行拷贝，如果是基本类型就拷贝的值，
// 如果是引用类型就拷贝的是内存地址,对属性直接赋值不影响原对象，对指向同一内存地址的属性的值进行赋值就会影响
// 赋值相当于少了一步，直接是拷贝内存地址，当指向同一内存地址的属性发生改变会影响原对象
如何在JavaScript中实现不可变对象
// 1. 深克隆，但是深克隆的性能非常差，不适合于规模使用 
// 2. Immutable.js，Immutable.js是自成一体的一套数据结构，性能良好，但是需要学习额外的API 
    // 它将所有的原生数据类型（Object， Array等）都会转化成 immutable-js 的内部对象（Map，List 等），并且任何操作最终都会返回一个新的 immutable 的值。
// 3. immer，利用Proxy特性，不需学习额外的api，性能良好
JavaScript的基本类型和复杂类型是储存在哪的
// 基本数据类型会储存在栈中，但如果被闭包引用则会常驻在内存堆中 复杂数据类型会储存在堆中
讲讲JavaScript垃圾回收是怎么做的
// 标记-清除
// 把所有活动对象做上标记,把没有标记（也就是非活动对象）销毁,
// 从全局作用域的变量，沿作用域逐层往里深度遍历,当发现被引用,打上标记,执行完毕,将没有被标记的变量内存,进行销毁
常见的内存泄漏
// 意外的全局变量
// 被遗忘的定时器和回调函数
// 闭包
// DOM 引用